#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using C5;
using Grawlix.Concurrency;
using log4net;

namespace Grawlix.Services
{
	/// <summary>
	/// A wrapper for each isolated component.
	/// </summary>
	public sealed class Domain : PermanentMarshalByRefObject,
	                             IDisposable
	{
		/// <summary>
		/// The log used to sent output information.
		/// </summary>
		private static readonly ILog _log =
			LogManager.GetLogger(typeof(Domain));

		/// <summary>
		/// The fully-qualified governor type name.
		/// </summary>
		private readonly string _governorName;

		/// <summary>
		/// The name of this domain.
		/// </summary>
		private readonly string _name;

		/// <summary>
		/// The object to synchronize on.
		/// </summary>
		private readonly object _sync = new object();

		/// <summary>
		/// The constraints to put onto the loaded governor type.
		/// </summary>
		private ArrayList<Type> _constraints =
			new ArrayList<Type>();

		/// <summary>
		/// The time of the last update.
		/// </summary>
		private TimeSpan _currentTime = new TimeSpan(0);

		/// <summary>
		/// Whether the object has been disposed.
		/// </summary>
		private bool _disposed;

		/// <summary>
		/// The queue of events to fire.
		/// </summary>
		private C5.LinkedList<Message> _eventQueue =
			new C5.LinkedList<Message>();

		/// <summary>
		/// The asynchronous load operation.
		/// </summary>
		private Future _future;

		/// <summary>
		/// The governor of this domain.
		/// </summary>
		private IGovernor _governor;

		/// <summary>
		/// The kernel to access services from.
		/// </summary>
		private IGameKernel _kernel;

		/// <summary>
		/// The queue of messages which haven't expired.
		/// </summary>
		private IntervalHeap<TimedMessage> _timedQueue =
			new IntervalHeap<TimedMessage>(
				new TimedMessageTimeComparer());

		/// <summary>
		/// Initializes a new instance of the <see cref="Domain"/>
		/// class.
		/// </summary>
		/// <param name="name">The name off the domain.</param>
		/// <param name="governorName">Fully qualified governor name.
		/// </param>
		/// <param name="constraints">The constraints to put on the
		/// governor type.</param>
		/// <exception cref="ArgumentNullException"><c>constraints</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException"><c>governorName</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException"><c>name</c> is
		/// null.</exception>
		/// <exception cref="ArgumentException">constraints contains
		/// non-interface constraints.</exception>
		public Domain(string name, string governorName,
		              IEnumerable<Type> constraints)
		{
			if (constraints == null)
				throw new ArgumentNullException("constraints");
			if (governorName == null)
				throw new ArgumentNullException("governorName");
			if (name == null)
				throw new ArgumentNullException("name");
			_name = name;
			_governorName = governorName;
			_constraints.Add(typeof(IGovernor));
			foreach (var constraint in constraints)
			{
				if (!constraint.IsInterface)
				{
					throw new ArgumentException(
						"Unable to create a <Domain> with " +
						"non-interface constraint " + constraint + ".",
						"constraints");
				}
				_constraints.Add(constraint);
			}
		}

		/// <summary>
		/// Gets the constraints for the governor.
		/// </summary>
		/// <value>The interfaces that the governor must inherit from.
		/// </value>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public C5.IList<Type> Constraints
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("Domain");
				return new GuardedList<Type>(_constraints);
			}
		}

		/// <summary>
		/// Gets any failure of the Domain, once the domain has stopped
		/// loading.
		/// </summary>
		/// <value>The failure or <code>null</code>.</value>
		/// <exception cref="InvalidOperationException">The Domain has
		/// not yet finished loading.</exception>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public Exception Failure
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("Domain");
				if (!Ready)
				{
					throw new InvalidOperationException(
						"Attempt to check on the failure result of " +
						"a domain (" + this + ") " +
						"when it is not finished loading. " +
						"First check the Ready property to see " +
						"if the load is finished.");
				}
				return _future.Failure;
			}
		}

		/// <summary>
		/// Gets the kernel.
		/// </summary>
		/// <value>The kernel that provides services to the Domain.
		/// </value>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public IGameKernel Kernel
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("Domain");
				return _kernel;
			}
		}

		/// <summary>
		/// Gets the name of the domain.
		/// </summary>
		/// <value>The name of this domain.</value>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public string Name
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("Domain");
				return _name;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this <see cref="Domain"/>is
		/// ready.
		/// </summary>
		/// <value><c>true</c> if ready; otherwise, <c>false</c>.
		/// </value>
		/// <remarks>Could also indicate that the <see cref="Domain"/>
		/// failed to load.</remarks>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public bool Ready
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("Domain");
				if (_future == null)
					return false;
				return _future.IsFinished;
			}
		}

		/// <summary>
		/// Activates this instance.
		/// </summary>
		/// <param name="kernel">The kernel to load services from.
		/// </param>
		/// <remarks>This function should be called automatically.
		/// </remarks>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		/// <exception cref="ArgumentNullException"><c>kernel</c> is
		/// null.</exception>
		public void Activate(IGameKernel kernel)
		{
			if (kernel == null)
				throw new ArgumentNullException("kernel");
			if (_disposed)
				throw new ObjectDisposedException("Domain");
			Thread.MemoryBarrier();
			if (_future != null)
				return;
			_kernel = kernel;
			_future = Future.Fork(() =>
				{
					LoadGovernor();
					Thread.MemoryBarrier();
				});
			Thread.MemoryBarrier();
		}

		/// <summary>
		/// Gives a proxy to the governor, casting it to a given
		/// constraint.
		/// </summary>
		/// <typeparam name="T">The type to constrain the governor to.
		/// </typeparam>
		/// <returns>The cast proxy to the governor.</returns>
		/// <exception cref="InvalidOperationException">A Domain must
		/// be ready before its governor is queried.</exception>
		/// <exception cref="ArgumentException">
		/// <c>ArgumentException</c>.</exception>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		[SuppressMessage("Microsoft.Design", "CA1004",
			Justification =
				"A generic parameter provides type safety.")]
		public T DirectAccess<T>()
		{
			if (_disposed)
				throw new ObjectDisposedException("Domain");
			if (!Ready)
			{
				throw new InvalidOperationException(
					"A Domain must be ready before its governor is queried.");
			}
			if (Failure != null)
			{
				throw new InvalidOperationException(
					"Attempt to use " + this + " after a failed load.",
					Failure);
			}
			if (!_constraints.Contains(typeof(T)))
			{
				throw new ArgumentException("The generic parameter (" +
				                            typeof(T) + ") must be " +
				                            "one of the constraints supplied " +
				                            "at Domain initialization.");
			}
			// TODO: Proxy governor when requested by constraint.
			return (T)_governor;
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing,
		/// releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed
		/// and unmanaged resources; <c>false</c> to release only
		/// unmanaged resources.</param>
		[SuppressMessage("Microsoft.Usage", "CA2213",
			Justification =
				"C5 disposable containers should generally not need disposal."
			)]
		private void Dispose(bool disposing)
		{
			// NOTE: There may be a concurrency issue here.
			if (!_disposed)
			{
				if (disposing)
				{
					if (_governor != null)
					{
						var governor = _governor as IDisposable;
						if (governor != null)
							governor.Dispose();
					}
					if (_future != null)
					{
						_future.Cancel();
						_future = null;
					}
					_constraints = null;
					_eventQueue = null;
					_timedQueue = null;
				}
				_disposed = true;
			}
		}

		/// <summary>
		/// Grabs the messages that have been added since last update.
		/// </summary>
		/// <param name="elapsed">The elapsed time since last update.
		/// </param>
		/// <returns>The new messages.</returns>
		private C5.LinkedList<Message> GrabMessages(
			TimeSpan elapsed)
		{
			lock (_sync)
			{
				C5.LinkedList<Message> grab = _eventQueue;
				_eventQueue = new C5.LinkedList<Message>();
				_currentTime += elapsed;
				while (!_timedQueue.IsEmpty &&
				       (_currentTime < _timedQueue.FindMin().Time))
					grab.Enqueue(_timedQueue.DeleteMin().Message);
				return grab;
			}
		}

		/// <summary>
		/// Loads the governor.
		/// </summary>
		/// <returns>The newly loaded governor.</returns>
		/// <exception cref="ArgumentNullException"><c>governorName</c>
		/// is null.</exception>
		/// <exception cref="DomainLoadException">An exception was
		/// encountered during loading.</exception>
		private void LoadGovernor()
		{
			Debug.Assert(_governor == null);
			Type governorType;
			try
			{
				governorType = Type.GetType(_governorName, true);
			}
			catch (Exception e)
			{
				throw new DomainLoadException(
					"Could not find user governor (" + _governorName +
					"). " + "Make sure the containing assembly is " +
					"accessible from this environment.", e);
			}
			foreach (var constraint in _constraints)
			{
				if (!constraint.IsAssignableFrom(governorType))
				{
					throw new DomainLoadException("The governor type (" +
					                              governorType + ") from " +
					                              "the user-provided assembly (" +
					                              _governorName +
					                              ") does not " +
					                              "properly implement " +
					                              constraint + ". " +
					                              "Ensure that the governor implements " +
					                              "the interfaces required for its use.");
				}
			}
			try
			{
				_governor =
					(IGovernor)
					Activator.CreateInstance(governorType, _kernel);
			}
			catch (Exception exception)
			{
				throw new DomainLoadException(
					"Unable to construct an instance of " +
					"the governor (" + governorType.FullName + ").\n",
					exception);
			}
		}

		/// <summary>
		/// Posts the specified message to the message queue.
		/// </summary>
		/// <param name="message">The message to post.</param>
		/// <exception cref="ArgumentNullException"><c>message</c> is
		/// null.</exception>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void Post(Message message)
		{
			if (_disposed)
				throw new ObjectDisposedException("Domain");
			if (message == null)
				throw new ArgumentNullException("message");
			lock (_sync)
			{
				_eventQueue.Enqueue(message);
			}
		}

		/// <summary>
		/// Posts the specified message to the message queue after a
		/// set time.
		/// </summary>
		/// <param name="message">The message to post.</param>
		/// <param name="expire">The time until the post.</param>
		/// <exception cref="ArgumentNullException"><c>message</c> is
		/// null.</exception>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void Post(Message message, TimeSpan expire)
		{
			if (message == null)
				throw new ArgumentNullException("message");
			lock (_sync)
			{
				if (_disposed)
					throw new ObjectDisposedException("Domain");
				var timed = new TimedMessage
					{Message = message, Time = (expire + _currentTime)};
				_timedQueue.Add(timed);
			}
		}

		/// <summary>
		/// Runs the governor on the specified message.
		/// </summary>
		/// <param name="message">The message to run on.</param>
		[SuppressMessage("Microsoft.Design", "CA1031",
			Justification = "")]
		private void SendMessage(Message message)
		{
			Debug.Assert(Ready);
			try
			{
				_governor.SendMessage(message);
			}
			catch (Exception exception)
			{
				if (_log.IsWarnEnabled)
				{
					_log.Warn(
						"Exception received from " + "the user governor (" +
						_governor + ") in " + "domain " + Name + " " +
						"resulting from an attempt to process a message.",
						exception);
				}
			}
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the
		/// current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current
		/// <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			return "Domain-" + Name;
		}

		/// <summary>
		/// Updates the Domain, processing all messages.
		/// </summary>
		/// <param name="elapsed">The elapsed time since last update.</param>
		/// <exception cref="ObjectDisposedException">The object has
		/// already been disposed.</exception>
		public void Update(TimeSpan elapsed)
		{
			if (_disposed)
				throw new ObjectDisposedException("Domain");
			if (Ready && Failure == null)
			{
				_governor.Update(elapsed);
				foreach (var message in GrabMessages(elapsed))
					SendMessage(message);
			}
		}
	}
}